package com.sh.data.engine.domain.dataasset.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.EnumUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import com.sh.data.engine.common.constants.Constants;
import com.sh.data.engine.common.enumDefinition.DSType;
import com.sh.data.engine.common.exception.BusinessException;
import com.sh.data.engine.common.util.NumberUtil;
import com.sh.data.engine.common.util.TableNameUtil;
import com.sh.data.engine.domain.authority.service.ProjectTblService;
import com.sh.data.engine.domain.base.model.PageResult;
import com.sh.data.engine.domain.base.model.UserContext;
import com.sh.data.engine.domain.common.service.CommonService;
import com.sh.data.engine.domain.dataasset.model.domain.TableInfoBizDomain;
import com.sh.data.engine.domain.dataasset.model.domain.TblPropEntityKey;
import com.sh.data.engine.domain.dataasset.model.enums.ModelLevelEnum;
import com.sh.data.engine.domain.dataasset.model.param.QueryTableParam;
import com.sh.data.engine.domain.dataasset.service.TableService;
import com.sh.data.engine.domain.dataasset.service.TblPropService;
import com.sh.data.engine.domain.shims.db.BaseDbManager;
import com.sh.data.engine.domain.shims.db.model.TableInfoDomain;
import com.sh.data.engine.domain.workspace.manager.service.UserService;
import com.sh.data.engine.repository.dao.authority.entity.ProjectTblEntity;
import com.sh.data.engine.repository.dao.dataasset.entity.TblPropEntity;
import com.sh.data.engine.repository.dao.workspace.manager.entity.StorageEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author: zigui.zdf @Date: 2022/2/24 20:54:19 @Description:
 */
@Service
@Slf4j
public class TableServiceImpl implements TableService {

    @Autowired
    private CommonService commonService;

    @Autowired
    private TblPropService tblPropService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private UserService userService;

    @Resource
    private ProjectTblService projectTblService;

    @Override
    public PageResult<TableInfoBizDomain> getTablePage(Long projectId, QueryTableParam param) {
        final StorageEntity storage = commonService.getStorage(projectId);

        final BaseDbManager baseDbManager = commonService.getStorageDbManagerByProjctId(projectId);

        param = Optional.ofNullable(param).orElse(new QueryTableParam());

        try {
            List<TableInfoDomain> tableList =
                baseDbManager.getTableAndViewList(storage.getRmdbs().getDbName(),
                    storage.getRmdbs().getSchema());

            if (CollectionUtils.isNotEmpty(param.getTableNamePrefixList())) {
                tableList = filterByPrefix(tableList, param.getTableNamePrefixList());
            } else if (CollectionUtils.isNotEmpty(param.getTableNameReversePrefixList())) {
                tableList = filterByReversePrefix(tableList, param.getTableNameReversePrefixList());
            } else {
                tableList = filterByPrefix(tableList, param.getTableNamePrefix());
            }

            tableList = filterByTableNameOrComment(tableList, param.getSearchStr());

            List<TableInfoBizDomain> bizInfoList = fillBizInfo(tableList, projectId);

            bizInfoList = filterByThemeId(bizInfoList, param.getThemeIdList());

            String userId = UserContext.getUserId();
            if (userId != null) {
                LambdaQueryWrapper<ProjectTblEntity> wrapper =
                    new LambdaQueryWrapper<ProjectTblEntity>()
                        .eq(ProjectTblEntity::getProjectId, projectId)
                        .eq(ProjectTblEntity::getUserId, userId);
                Map<Long, String> collect =
                    projectTblService.list(wrapper).stream()
                        .collect(Collectors.toMap(ProjectTblEntity::getTblId, ProjectTblEntity::getAuth));
                if (CollUtil.isNotEmpty(collect)) {
                    bizInfoList =
                        bizInfoList.stream()
                            .filter(
                                table ->
                                    !collect.containsKey(table.getTblId())
                                        || (collect.containsKey(table.getTblId())
                                        && !"2".equals(collect.get(table.getTblId()))))
                            .collect(Collectors.toList());
                }
            }

            if (CollectionUtils.isEmpty(bizInfoList)) {
                return new PageResult<>();
            }

            final String sortOrder = param.getOrder();
            final String sortField = param.getSortField();
            Integer pageSize = param.getPageSize();
            Integer pageNum = param.getPageNum();

            if (StringUtils.isNotBlank(sortField) && StringUtils.isNotBlank(sortOrder)) {
                Collections.sort(bizInfoList, getComparator(sortField, sortOrder));
            }

            int totalSize = bizInfoList.size();
            List<List<TableInfoBizDomain>> pageList = Lists.partition(bizInfoList, pageSize);

            int originPageNum = pageNum;
            pageNum = pageNum - 1;
            if (pageNum < 0) {
                pageNum = 0;
                originPageNum = pageNum;
            }
            if (pageNum > pageList.size() - 1) {
                pageNum = pageList.size() - 1;
                originPageNum = pageNum;
            }

            List<TableInfoBizDomain> subList = pageList.get(pageNum);

            int totalPages =
                totalSize % pageSize == 0 ? totalSize / pageSize : (totalSize / pageSize) + 1;

            PageResult<TableInfoBizDomain> pageInfo = new PageResult<>();
            pageInfo.setResult(subList);
            pageInfo.setPageNum(originPageNum);
            pageInfo.setPageSize(pageSize);
            pageInfo.setTotalPages(totalPages);
            pageInfo.setTotalElements(Long.valueOf(totalSize));

            return pageInfo;

        } catch (SQLException e) {
            log.error("查询表列表异常", e);
            return new PageResult<>();
        } finally {
            if (null != baseDbManager) {
                baseDbManager.close();
            }
        }
    }

    @Override
    public List<TableInfoBizDomain> getTableList(Long projectId, QueryTableParam param) {
        final StorageEntity storage = commonService.getStorage(projectId);

        final BaseDbManager baseDbManager = commonService.getStorageDbManagerByProjctId(projectId);

        param = Optional.ofNullable(param).orElse(new QueryTableParam());

        try {
            List<TableInfoDomain> tableList = Lists.newArrayList();
            if (baseDbManager.hasSchema()) {
                tableList = baseDbManager.getTableList(storage.getRmdbs().getDbName(),
                    storage.getRmdbs().getSchema());
            } else {
                tableList = baseDbManager.getTableList(storage.getRmdbs().getDbName());
            }

            tableList = filterBySchema(tableList, param.getSchema());

            tableList = filterByPrefix(tableList, param.getTableNamePrefix());

            tableList = filterByPrefix(tableList, param.getTableNamePrefixList());

            tableList = filterByTableNameOrComment(tableList, param.getSearchStr());

            List<TableInfoBizDomain> bizInfoList = fillBizInfo(tableList, projectId);

            bizInfoList = filterByThemeId(bizInfoList, param.getThemeIdList());

            if (CollectionUtils.isEmpty(bizInfoList)) {
                return Collections.EMPTY_LIST;
            }

            final String sortOrder = param.getOrder();
            final String sortField = param.getSortField();

            if (StringUtils.isNotBlank(sortField) && StringUtils.isNotBlank(sortOrder)) {
                Collections.sort(bizInfoList, getComparator(sortField, sortOrder));
            }

            if (baseDbManager.hasSchema()) {
                // 如果底座是带schema的数据库，那么要把表名拼上schema
                for (TableInfoBizDomain tableInfoBizDomain : bizInfoList) {
                    if (StringUtils.isNotBlank(tableInfoBizDomain.getSchema())) {
                        tableInfoBizDomain.setTableName(
                            tableInfoBizDomain.getSchema() + "." + tableInfoBizDomain.getTableName());
                    }
                }
            }

            return bizInfoList;

        } catch (SQLException e) {
            log.error("查询表列表异常", e);
            return Collections.EMPTY_LIST;
        } finally {
            if (null != baseDbManager) {
                baseDbManager.close();
            }
        }
    }

    /**
     * @param projectId
     * @param tableName
     * @description : 查询表信息
     * @author : tinglan.ys
     * @date : 2022/5/10
     */
    @Override
    public TableInfoDomain getTableInfo(Long projectId, String tableName) {

        final StorageEntity storage = commonService.getStorage(projectId);

        final BaseDbManager baseDbManager = commonService.getStorageDbManagerByProjctId(projectId);

        String storageType = storage.getStorageType();

        String dbName = storage.getRmdbs().getDbName();

        if (StringUtils.isEmpty(dbName)) {
            return null;
        }
        TableInfoDomain tableInfoDomain = null;
        try {
            if (storageType.equals(DSType.PostgreSQL.name()) || storageType.equals(DSType.Sap.name())) {
                tableInfoDomain =
                    baseDbManager.getTableInfoByTableName(
                        dbName,
                        TableNameUtil.getTblSchema(tableName),
                        TableNameUtil.getTblNameShort(tableName));
            } else {
                tableInfoDomain = baseDbManager.getTableInfoByTableName(dbName, tableName);
            }
        } catch (SQLException throwable) {
            throw new BusinessException("查询不到该信息，查询失败：" + throwable.toString());
        } finally {
            baseDbManager.close();
        }
        return tableInfoDomain;
    }

    @Override
    public TableInfoBizDomain getTableBizInfo(Long projectId, String tableName) {
        QueryTableParam param = new QueryTableParam();
        param.setSearchStr(tableName);
        List<TableInfoBizDomain> tableList = this.getTableList(projectId, param);
        return CollUtil.isEmpty(tableList) ? null : tableList.get(0);
    }

    private Comparator<TableInfoBizDomain> getComparator(String sortField, String sortOrder) {
        final String _sortField = StringUtils.upperCase(sortField);
        return Comparator.nullsFirst(
            new Comparator<TableInfoBizDomain>() {
                @Override
                public int compare(TableInfoBizDomain o1, TableInfoBizDomain o2) {
                    switch (_sortField) {
                        case "TABLECOMMENT":
                            return nullsCompare(o1.getTableComment(), o2.getTableComment(), sortOrder, true);
                        case "TABLESIZE":
                            return nullsCompare(o1.getTableSize(), o2.getTableSize(), sortOrder, true);
                        case "LASTDDLTIME":
                            return nullsCompare(o1.getLastDdlTime(), o2.getLastDdlTime(), sortOrder, true);
                        case "LIFECYCLE":
                            return nullsCompare(o1.getLifecycle(), o2.getLifecycle(), sortOrder, true);
                        case "TABLENAME":
                            return nullsCompare(o1.getTableName(), o2.getTableName(), sortOrder, true);
                        case "UPDATETIME":
                            return nullsCompare(o1.getUpdateTime(), o2.getUpdateTime(), sortOrder, true);
                        default:
                            return nullsCompare(o1.getTableName(), o2.getTableName(), sortOrder, true);
                    }
                }
            });
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    private int nullsCompare(Comparable a, Comparable b, String sortOrder, boolean nullFirst) {
        if (a == null) {
            return (b == null) ? 0 : (nullFirst ? -1 : 1);
        } else if (b == null) {
            return nullFirst ? 1 : -1;
        } else {
            return "asc".equalsIgnoreCase(sortOrder) ? a.compareTo(b) : b.compareTo(a);
        }
    }

    private List<TableInfoDomain> filterByPrefix(
        List<TableInfoDomain> tableList, List<String> tableNamePrefixList) {
        if (CollectionUtils.isEmpty(tableList)) {
            return Collections.emptyList();
        }
        if (CollectionUtils.isEmpty(tableNamePrefixList)) {
            return tableList;
        }
        List<TableInfoDomain> tableInfoDomains = new LinkedList<>();
        for (TableInfoDomain tableInfoDomain : tableList) {
            for (String tableNamePrefix : tableNamePrefixList) {
                if (StringUtils.startsWithIgnoreCase(tableInfoDomain.getTableName(), tableNamePrefix)) {
                    tableInfoDomains.add(tableInfoDomain);
                    break;
                }
            }
        }
        return tableInfoDomains;
    }

    /**
     * filter all table not in (ods, dwd, dws, dim, ads)
     *
     * @param tableList
     * @param tableNamePrefixList
     * @return
     */
    private List<TableInfoDomain> filterByReversePrefix(
        List<TableInfoDomain> tableList, List<String> tableNamePrefixList) {
        if (CollectionUtils.isEmpty(tableList)) {
            return Collections.emptyList();
        }

        List<TableInfoDomain> tableInfoDomains = new LinkedList<>();
        outer:
        for (TableInfoDomain tableInfoDomain : tableList) {
            for (String tableNamePrefix : tableNamePrefixList) {
                if (StringUtils.startsWithIgnoreCase(tableInfoDomain.getTableName(), tableNamePrefix)) {
                    continue outer;
                }
            }
            tableInfoDomains.add(tableInfoDomain);
        }
        return tableInfoDomains;
    }

    private List<TableInfoDomain> filterBySchema(List<TableInfoDomain> tableList, String schema) {
        if (StringUtils.isBlank(schema)) {
            return tableList;
        }

        if (CollectionUtils.isEmpty(tableList)) {
            return Collections.emptyList();
        }

        return tableList.stream()
            .filter(Objects::nonNull)
            .filter(e -> StringUtils.equals(e.getSchema(), schema))
            .collect(Collectors.toList());
    }

    private List<TableInfoDomain> filterByPrefix(
        List<TableInfoDomain> tableList, String tableNamePrefix) {
        if (StringUtils.isBlank(tableNamePrefix)) {
            return tableList;
        }
        if (CollectionUtils.isEmpty(tableList)) {
            return Collections.emptyList();
        }

        return tableList.stream()
            .filter(Objects::nonNull)
            .filter(e -> StringUtils.startsWithIgnoreCase(e.getTableName(), tableNamePrefix))
            .collect(Collectors.toList());
    }

    private List<TableInfoDomain> filterByTableNameOrComment(
        List<TableInfoDomain> list, String searchStr) {
        if (StringUtils.isBlank(searchStr)) {
            return list;
        }
        if (CollectionUtils.isEmpty(list)) {
            return Collections.emptyList();
        }

        final String _searchStr = StringUtils.trim(searchStr);

        return list.stream()
            .filter(Objects::nonNull)
            .filter(
                e ->
                    StringUtils.containsIgnoreCase(e.getTableName(), _searchStr)
                        || StringUtils.containsIgnoreCase(e.getTableComment(), _searchStr)
                        || StringUtils.containsIgnoreCase(e.getSchema(), _searchStr))
            .collect(Collectors.toList());
    }

    private List<TableInfoBizDomain> filterByThemeId(
        List<TableInfoBizDomain> list, List<Long> themeIdList) {
        if (CollectionUtils.isEmpty(themeIdList)) {
            return list;
        }
        if (CollectionUtils.isEmpty(list)) {
            return Collections.EMPTY_LIST;
        }

        return list.stream()
            .filter(Objects::nonNull)
            .filter(e -> e.getThemeId() != null && themeIdList.contains(e.getThemeId()))
            .collect(Collectors.toList());
    }

    private List<TableInfoBizDomain> fillBizInfo(List<TableInfoDomain> list, Long projectId) {
        if (CollectionUtils.isEmpty(list)) {
            return Collections.EMPTY_LIST;
        }

        final List<String> tableNameList =
            list.stream().map(TableInfoDomain::getTableName).collect(Collectors.toList());

        final List<TblPropEntity> tblPropList =
            tblPropService.getTblPropListByTblNameList(projectId, tableNameList);

        final Map<TblPropEntityKey, TblPropEntity> propEntityMap =
            tblPropList.stream()
                .collect(
                    Collectors.toMap(
                        x -> {
                            TblPropEntityKey tblPropEntityKey = new TblPropEntityKey();
                            tblPropEntityKey.setSchema(
                                StringUtils.isEmpty(x.getSchema()) ? "" : x.getSchema());
                            tblPropEntityKey.setTableName(x.getTableName());
                            return tblPropEntityKey;
                        },
                        x -> x,
                        (x, y) -> x));

        final List<String> creatorIds =
            tblPropList.stream().map(t -> t.getCreatorId()).distinct().collect(Collectors.toList());

        final Map<String, String> mapByUserIds = userService.getMapByUserIds(creatorIds);

        List<TableInfoBizDomain> domains = new ArrayList<>();

        List<TblPropEntity> insertList = new ArrayList<>();

        // fix 10w tables network timeout
        Map<Object, Object> tableSizeEntries =
            redisTemplate
                .opsForHash()
                .entries(String.format(Constants.RedisKey.PROJECT_TABLE_STORAGE_SIZE_EXP, projectId));

        for (TableInfoDomain tableInfoDomain : list) {
            final TableInfoBizDomain tableInfoBizDomain = new TableInfoBizDomain();
            tableInfoBizDomain.setDbName(tableInfoDomain.getDbName());
            tableInfoBizDomain.setSchema(tableInfoDomain.getSchema());
            tableInfoBizDomain.setTblId(tableInfoDomain.getTblId());
            tableInfoBizDomain.setTableName(tableInfoDomain.getTableName());
            tableInfoBizDomain.setTableComment(tableInfoDomain.getTableComment());
            tableInfoBizDomain.setTblType(tableInfoDomain.getTblType());
            tableInfoBizDomain.setLastDdlTime(tableInfoDomain.getLastDdlTime());
            tableInfoBizDomain.setPartitionTable(tableInfoDomain.isPartitionTable());

            String tblNamePrefix = tableInfoDomain.getTableName().split("_")[0];
            String modelType = "";
            try {
                ModelLevelEnum modelLevelEnum = EnumUtil.fromString(ModelLevelEnum.class,
                    tblNamePrefix.toUpperCase());
                modelType = modelLevelEnum.getCdmName();
            } catch (Exception e) {
                modelType = "未知";
            }
            tableInfoBizDomain.setModelType(modelType);

            domains.add(tableInfoBizDomain);

            TblPropEntityKey tblPropEntityKey = new TblPropEntityKey();
            tblPropEntityKey.setSchema(
                StringUtils.isEmpty(tableInfoDomain.getSchema()) ? "" : tableInfoDomain.getSchema());
            tblPropEntityKey.setTableName(tableInfoDomain.getTableName());
            TblPropEntity tblPropEntity = propEntityMap.get(tblPropEntityKey);

            if (null == tblPropEntity) {
                // 若不存在，则生成相关数据
                tblPropEntity = new TblPropEntity();
                tblPropEntity.setProjectId(projectId);
                tblPropEntity.setTblType(
                    tableInfoDomain.getTblType() == null ? "" : tableInfoDomain.getTblType().name());
                tblPropEntity.setSchema(tableInfoBizDomain.getSchema());
                tblPropEntity.setTableName(tableInfoDomain.getTableName());
                tblPropEntity.setTableComment(tableInfoDomain.getTableComment());
                // default -1
                tblPropEntity.setLifecycle(-1);
                tblPropEntity.setRowState(1);
                tblPropEntity.setTblId(tblPropService.generateTblId(projectId));
                insertList.add(tblPropEntity);
            }

            tableInfoBizDomain.setId(tblPropEntity.getId());
            tableInfoBizDomain.setTblId(tblPropEntity.getTblId());
            tableInfoBizDomain.setTblIdStr(StringUtils.join(tblPropEntity.getTblId()));
            tableInfoBizDomain.setThemeId(tblPropEntity.getThemeId());
            tableInfoBizDomain.setLifecycle(tblPropEntity.getLifecycle());
            tableInfoBizDomain.setCreatorId(tblPropEntity.getCreatorId());
            tableInfoBizDomain.setCreatorUsername(
                tblPropEntity.getCreatorId() == null
                    ? null
                    : mapByUserIds.get(tblPropEntity.getCreatorId()));
            tableInfoBizDomain.setUpdateTime(tblPropEntity.getUpdateTime());

            String tableName = tableInfoDomain.getTableName();
            if (StringUtils.isNotBlank(tableInfoDomain.getSchema())) {
                tableName = tableInfoDomain.getSchema() + "." + tableInfoDomain.getTableName();
            }

            final Object o = tableSizeEntries.get(String.format("%s", tableName));
            long tableSize = o == null ? 0 : Long.valueOf(o.toString());

            tableInfoBizDomain.setTableSize(tableSize);
            tableInfoBizDomain.setShowTableSize(NumberUtil.sizeFormatNum2String(tableSize));
            tableInfoBizDomain.setDataSecretId(tblPropEntity.getDataSecretId());
        }

        tblPropService.insertBatch(insertList);

        return domains;
    }
}
